//
// Created by LONG on 2020/10/19.
//

#include "../include/l3Api.h"
#include "uem/include/uemApi.h"
#include "../../lcore/misc/misc.h"

S_UeL3Context *gpUeL3Context = NULL;

int32_t UE_L3_LoadUeInstConfig(S_UeL3UeInst *pUeInst, S_UeInstContext *pUeInstContext)
{
    pUeInst->inUse = pUeInstContext->inUse;

    return O5G_RC_OK;
}

int32_t UE_L3_LoadConfig()
{
    int32_t rc;
    uint32_t loop;

    gpUeL3Context->ueInstNum = gpUeContext->ueInstNum;
    for (loop = 0; loop < gpUeContext->ueInstNum; loop++)
    {
        rc = UE_L3_LoadUeInstConfig(&gpUeL3Context->ueInst[loop], &gpUeContext->ueInstContext[loop]);
        IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_LoadConfig() UE_L3_LoadUeInstConfig() error!\n");
    }

    return O5G_RC_OK;
}

int32_t UE_L3_Init(void)
{
    int32_t rc;

    gpUeL3Context = OS_MemCalloc(1, sizeof(S_UeL3Context));
    IF_RETURN(==, NULL, gpUeL3Context, O5G_RC_ER, "UE_L3_Init() OS_MemCalloc() error!\n");

    rc = UE_L3_LoadConfig();
    IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_Init() UE_L3_LoadConfig() error!\n");

    UE_INIT_INST_IDX(gpUeL3Context);

    rc = OS_SelectInit(&gpUeL3Context->selector, NULL);
    IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_Init() OS_SelectInit() error!\n");

    rc = OS_UdpInit(&gpUeL3Context->l2SockFd);
    IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_Init() OS_UdpInit(l2SockFd) error!\n");

    rc = OS_UdpBind(gpUeL3Context->l2SockFd, "127.0.0.1", UE_L2_2_L3_UDP_PORT);
    IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_Init() OS_UdpBind(l2SockFd) error!\n");

    rc = OS_UdpConnect(gpUeL3Context->l2SockFd, "127.0.0.1", UE_L3_2_L2_UDP_PORT);
    IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_Init() OS_UdpConnect(l2SockFd) error!\n");

    gpUeL3Context->poller = POLLER_Create(POLL_FD_NUM_MAX);
    IF_RETURN(==, NULL, gpUeL3Context->poller, O5G_RC_ER, "UE_L3_Init() POLLER_Create() error!\n");

    rc = UE_L3_UEM_Init();
    IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_Init() UE_L3_UEM_Init() error!\n");

    CSP_LOG_INF(UE_MD_L3_COMM, "UE_L3_Init() success.\n");

    return O5G_RC_OK;
}

int32_t UE_L3_Final(void)
{
    int32_t rc;

    rc = OS_SelectFinal(&gpUeL3Context->selector);
    IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_Final() OS_SelectFinal() error!\n");

    rc = OS_UdpFinal(gpUeL3Context->l2SockFd);
    IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_Final() OS_UdpFinal(l2SockFd) error!\n");

    POLLER_Destroy(gpUeL3Context->poller);
    
    rc = UE_L3_UEM_Final();
    IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_Final() UE_L3_UEM_Final() error!\n");

    OS_MemFree(gpUeL3Context);

    CSP_LOG_INF(UE_MD_L3_COMM, "UE_L3_Final() success.\n");

    return O5G_RC_OK;
}


void UE_L3_PrdProcess()
{
    UE_L3_UEM_PrdProcess();
}

void UE_L3_IndProcess(int32_t event, int fd, void *data)
{
    int32_t rc;

    S_OsSelectInd indMsg;
    rc = OS_SelectRecvByFd(fd, &indMsg);
    IF_RETURN(!=, O5G_RC_OK, rc, NO_RETURN_VALUE, "UE_L3_IndProcess() OS_SelectRecv() error!\n");

    switch (indMsg.ind)
    {
    case UE_L3_INTRA_IND_MSG_UE_FSM_EVT:
        UE_L3_UEM_IndProcess(&indMsg);
        break;
    default:
        CSP_LOG_WRN(UE_MD_L3_COMM, "UE_L3_IndProcess() recv unsupport ind[%u]\n", indMsg.ind);
        break;
    }
}

void UE_L3_MsgProcess(int32_t event, int fd, void *data)
{
    int32_t rc;
    S_UeCommMsgHdr *pCommMsgHdr;

    rc = recv(fd, gpUeL3Context->sockRecvBuffer, SOCK_BUFFER_SIZE, MSG_DONTWAIT);
    IF_RETURN(>, (int32_t)sizeof(S_UeCommMsgHdr), rc, NO_RETURN_VALUE, "UE_L3_MsgProcess() recv() error! rc[%d] < %u\n",
        rc, (uint32_t)sizeof(S_UeCommMsgHdr));

    pCommMsgHdr = (S_UeCommMsgHdr *)gpUeL3Context->sockRecvBuffer;
    switch (pCommMsgHdr->type)
    {
    case UE_L1_API_MSG_CONFIG_RSP:
    case UE_L2_L3_MSG_CONFIG_RSP:
    case UE_L1_API_MSG_SYNC_RSP:
    case UE_L2_L3_MSG_START_RSP:
        UE_L3_UEM_MsgProcess(pCommMsgHdr);
        break;
    default:
        CSP_LOG_WRN(UE_MD_L3_COMM, "UE_L3_MsgProcess() recv unsupport msg[%#x]\n", pCommMsgHdr->type);
        break;
    }
}

void *UE_L3_Task(void* arg)
{
    int fd;
    uint64_t lastTimeStamp;

    fd = gpUeL3Context->selector.recvFd;
    POLLER_Add(gpUeL3Context->poller, fd, UE_L3_IndProcess, NULL, NULL, NULL);

    fd = gpUeL3Context->l2SockFd;
    POLLER_Add(gpUeL3Context->poller, fd, UE_L3_MsgProcess, NULL, NULL, NULL);

    while (1)
    {
        POLLER_Poll(gpUeL3Context->poller, 1);

        if (CSP_ArrivalDelayTimeUs(&lastTimeStamp, 10000))
        {
            gpUeL3Context->tick10ms++;
            UE_L3_PrdProcess();
        }

        pthread_testcancel();
    }
    return NULL;
}

int32_t UE_L3_Start(void)
{
    int32_t rc;

    rc = OS_ThreadCreate(&gpUeL3Context->tid, UE_L3_Task, NULL);
    IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_Start() OS_ThreadCreate() error!\n");

    CSP_LOG_INF(UE_MD_L3_COMM, "UE_L3_Start() success.\n");

    return O5G_RC_OK;
}

int32_t UE_L3_Stop(void)
{
    int32_t rc;

    rc = OS_ThreadCancel(gpUeL3Context->tid);
    IF_RETURN(!=, O5G_RC_OK, rc, O5G_RC_ER, "UE_L3_Stop() OS_ThreadCancel() error!\n");

    CSP_LOG_INF(UE_MD_L3_COMM, "UE_L3_Stop() success.\n");
    
    return O5G_RC_OK;
}

